package xyz.paragon.product.controller

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import spock.lang.Narrative
import spock.lang.Specification
import spock.lang.Title
import xyz.paragon.product.event.EventProcessor
import xyz.paragon.product.persistence.domain.Product
import xyz.paragon.product.persistence.service.ProductService

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*

@Title("Product Controller Specification")
@Narrative("Specification of what product controller should be capable of")
class ProductControllerSpec extends Specification {

    def productService = Mock(ProductService.class)
    def eventProcessor = Mock(EventProcessor.class)
    def productController = new ProductController(productService, eventProcessor)
    MockMvc mockMvc = MockMvcBuilders.standaloneSetup(productController).build()

    def "when get is performed to '/' path then the response is 200(OK) and service method findAll() is called"() {
        when: "url path '/' is hit with GET request"
        def response = this.mockMvc.perform(get("/")).andReturn().response

        then: "response is 200(OK) and service method findAll() is called once"
        response.status == HttpStatus.OK.value()
        1 * this.productService.findAll()
    }

    def "when get is performed to '/{productId}' path then the response is 200(OK) and service method findById() is called"() {
        when: "url path '/{productId}' is hit with GET request"
        def response = this.mockMvc.perform(get("/1"))
                .andReturn()response

        then: "response is 200(OK) and service method findById(productId) is called once"
        response.status == HttpStatus.OK.value()
        1 * this.productService.findById(_ as Long)
    }

    def "when post is performed to '/' path then the response is 201(CREATED) and service method save(newProduct) is called"() {
        when: "url path '/' is hit with POST request"
        def mapper = new ObjectMapper()
        def response = this.mockMvc.perform(post("/")
                .contentType(MediaType.APPLICATION_JSON)
                .content(mapper.writeValueAsString(new Product())))
                .andReturn().response

        then: "response is 201(CREATED) and service method save(newProduct) is called once"
        1 * this.productService.save(_ as Product) >> {new Product(999L, "product_new", "description_new")}
        response.status == HttpStatus.CREATED.value()
    }

    def "when put is performed to '/{productId}' path then the response is 200(OK) and service method updateProduct(updatedProduct) is called"() {
        when: "url path '/{productId}' is hit with PUT request"
        def mapper = new ObjectMapper()
        def response = this.mockMvc.perform(put("/1")
                .contentType(MediaType.APPLICATION_JSON)
                .content(mapper.writeValueAsString(new Product())))
                .andReturn().response

        then: "response is 200(OK) and service method updateProduct(updatedProduct) is called once"
        response.status == HttpStatus.OK.value()
        1 * this.productService.save(_ as Product)
    }

    def "when delete is performed to '/{productId}' path then the response is 200(OK) and service method removeProduct(productId) is called"() {
        given:
        def channel = Stub(MessageChannel.class)
        channel.send(_ as Message) >> {true}

        when: "url path '/{productId}' is hit with DELETE request"
        def response = this.mockMvc.perform(delete("/1"))
                .andReturn().response

        then: "response is 200(OK) and service method removeProduct(productId) is called once"
        1 * this.eventProcessor.deleteProductEventChannel() >> {channel}
        response.status == HttpStatus.OK.value()
        1 * this.productService.deleteById(_ as Long)
    }
}
